package com.ease.util.collections {

   import com.ease.util.Diagnostics;
   import com.ease.util.ReflectionUtil;


   /**
    * A utility class that provides an object cache with type based lookup. It can be
    * configured to include base types as well as the concrete types of the cached objects.
    */
   public class TypeBasedObjectCache
   {
      private var _objectsByType:CompositeMap = new CompositeMap();

      //-------------------------------------------------------
      // Property: usesConcreteTypeOnly

      private var _usesConcreteTypeOnly:Boolean = true;

      /**
       * Indicates whether the cache should use only the concrete type of an object that is
       * added to the cache, or should also include the base types including the interfaces.
       */
      public function get usesConcreteTypeOnly():Boolean {
         return _usesConcreteTypeOnly;
      }

      /** @private */
      public final function set usesConcreteTypeOnly( value:Boolean ):void {
         if (_objectsByType.length != 0) {
            Diagnostics.assert( false, "Can change this setting only if cache is empty." );
            return;
         }
         _usesConcreteTypeOnly = value;
      }

      //-------------------------------------------------------

      /**
       * Indicates whether or not the given object is contained by this cache.
       */
      public final function contains( object:Object ):Boolean {
         return _objectsByType.contains( object );
      }

      /**
       * Adds the given object to the cache.
       *
       * @param object
       *    The object to be added to the cache.
       *
       * @internal Creates a mapping for every time implemented or extended by the given
       * object, except the root "Object" type.
       */
      public final function add( object:Object ):void {
         if (object == null) {
            return;
         }
         if (_objectsByType.contains( object )) {
            return;
         }
         var type:Class;
         if (_usesConcreteTypeOnly) {
            type = Object( object ).constructor;
            _objectsByType.add( type, object );
            return;
         }
         // Register the object against all these types: its class type, all the interfaces
         // it implements and all the classes it extends.
         var objectTypeNames:Array/*String*/ = ReflectionUtil.getTypes( object );
         for each (var typeName:String in objectTypeNames) {
            if (typeName == "Object") {
               // We don't want/need to create an explicit entry for the array of all objects
               // in this cache.
               continue;
            }
            type = ReflectionUtil.getDefinition( typeName, object );
            if (type == null) {
               continue;
            }
            _objectsByType.add( type, object );
         }
      }

      /**
       * Removes the given object from the cache.
       *
       * @param object
       *    The object to remove.
       */
      public final function remove( object:Object ):void {
         if (object == null) {
            return;
         }
         if (! _objectsByType.contains( object )) {
            return;
         }
         var type:Class;
         if (_usesConcreteTypeOnly) {
            type = Object( object ).constructor;
            _objectsByType.removeValue( type, object );
            return;
         }
         // unregister the object for all the types it's registered against
         var objectTypeNames:Array/*String*/ = ReflectionUtil.getTypes( object );
         for each (var typeName:String in objectTypeNames) {
            if (typeName == "Object") {
               // We don't create an explicit entry for the array of all objects in this
               // cache; hence, no need to remove either.
               continue;
            }
            type = ReflectionUtil.getDefinition( typeName, object );
            _objectsByType.removeValue( type, object );
         }
      }

      /**
       * Looks up the objects in the directory by the given type. Returns all objects from
       * the cache if the provided type is null or Object.
       */
      public final function getObjects( type:Class = null ):Array {
         if (type == Object) {
            type = null;
         }
         var objects:Array = _objectsByType.getValues( type );
         return objects;
      }
   } // class

}